/*
 * Copyright (c) 2011 Jean-Marc Perronne
 * Ensisa, 12 rue des Freres Lumieres, F-68093 Mulhouse cedex
 * All rights reserved.
 *
 * The copyright of this software is owned by Werner Randelshofer. 
 * You may not use, copy or modify this software, except in  
 * accordance with the license agreement you entered into with  
 * Jean-Marc Perronne. For details see accompanying license terms. 
 */

package fr.uha.ensisa.jmp.std.io;


public class BitBuffer
{
	private static final int[] BACK_MASK = {
		0x00, // 00000000
		0x01, // 00000001
		0x03, // 00000011
		0x07, // 00000111
		0x0F, // 00001111
		0x1F, // 00011111
		0x3F, // 00111111
		0x7F  // 01111111
	};
	
	private static final int[] FRONT_MASK = 
	{
		0x0000, // 00000000
		0x0080, // 10000000
		0x00C0, // 11000000
		0x00E0, // 11100000
		0x00F0, // 11110000
		0x00F8, // 11111000
		0x00FC, // 11111100
		0x00FE  // 11111110
	};
	
	private byte[] byteBuffer;
	private int currentByte;
	private int currentBit;
	private int eofByte;
	private boolean eofFlag;
	
	public BitBuffer(byte[] byteBuffer)
	{
		this.byteBuffer = byteBuffer;
		this.currentByte = 0;
		this.currentBit = 0;
		this.eofByte = byteBuffer.length;
	}
	
	public int getBits(int bitsToRead)
	{
		if (bitsToRead < 0) { throw new IllegalArgumentException("Bits to read may not be negative"); }
		if (bitsToRead == 0) return 0;
		if (eofFlag) return -1; // Already at end of file
		int toStore = 0;
		while (bitsToRead != 0 && !eofFlag)
		{
			if (currentBit < 0 || currentBit > 7) { throw new IllegalStateException("byte=" + currentByte + ", bit = " + currentBit); }

			// if we need to read from more than the current byte in the
			// buffer...
			int bitsLeft = 8 - currentBit;
			if (bitsToRead >= bitsLeft)
			{
				toStore <<= bitsLeft;
				bitsToRead -= bitsLeft;
				int cb = (int) byteBuffer[currentByte];
				if (currentBit == 0)
				{
					// we can read in a whole byte, so we'll do that.
					toStore += cb & 0xff;
				}
				else
				{
					// otherwise, only read the appropriate number of bits off
					// the back
					// side of the byte, in order to "finish" the current byte
					// in the
					// buffer.
					toStore += cb & BACK_MASK[bitsLeft];
					currentBit = 0;
				}
				currentByte++;
			}
			else
			{
				// We will be able to finish using the current byte.
				// read the appropriate number of bits off the front side of the
				// byte,
				// then push them into the int.
				toStore = toStore << bitsToRead;
				int cb = byteBuffer[currentByte] & 0xff;
				toStore += (cb & (0x00FF - FRONT_MASK[currentBit])) >> (bitsLeft - bitsToRead);
				currentBit += bitsToRead;
				bitsToRead = 0;
			}
			// If we reach the end of the buffer, return what we currently have.
			if (currentByte == eofByte)
			{
				eofFlag = true;
				return toStore;
			}
		}
		return toStore;
	}
	
	public void skipBits(long bits)
	{
		if (bits < 0) { throw new IllegalArgumentException("Bits to skip may not be negative"); }

		// handles skipping past eof
		if ((long) eofByte * 8 < (long) currentByte * 8 + currentBit + bits)
		{
			eofFlag = true;
			currentByte = eofByte;
			currentBit = 0;
			return;
		}

		int skipBytes = (int) (bits / 8);
		int skipBits = (int) (bits % 8);
		currentByte += skipBytes;
		currentBit += skipBits;
		while (currentBit >= 8)
		{
			currentByte++;
			currentBit -= 8;
		}
	}
	
	public static void main(String args[])
	{
		byte[] input = {-1};
		BitBuffer bs = new BitBuffer(input);
		
		System.out.println(bs.getBits(1));

		System.out.println(bs.getBits(7));
		System.out.println(bs.getBits(1));

		
		/*
		System.out.println(((byte)-1));

		System.out.println(Format.unsignedByte((byte)-1));
		System.out.println(Format.unsignedShort((byte)-1));
		System.out.println(Format.unsignedInt(-1));
*/

		
	}
}
